/*
 * Aritmetico.cpp
 *
 *  Created on: 11/06/2013
 *      Author: juan
 */

#include "Aritmetico.h"

Aritmetico::Aritmetico() {
	this->particiones = new list<Particion*>();
	this->underflow_counter = 0;
	this->bb = new ByteBuffer();
	this->piso = 0;
	this->techo = pow (2,PRECISION_ARITMETICO);
	this->b_piso = bitset<PRECISION_ARITMETICO> (0);
	this->b_techo = bitset<PRECISION_ARITMETICO> (1);
	this->bd = NULL;
}

Aritmetico::~Aritmetico() {
	while (this->particiones->size() > 0){
		delete particiones->front();
		particiones->remove(particiones->front());
	}
	delete particiones;
	delete bb;
	if (bd != NULL)
		delete bd;
}


void Aritmetico::setup_descompresion(char *buffer_descompresion) {
	this->bd = new BufferDescompresion(buffer_descompresion);
	this->particiones = new list<Particion*>();
	this->underflow_counter = 0;
	this->piso = 0;
	this->techo = pow (2,PRECISION_ARITMETICO);
	this->b_piso = bitset<PRECISION_ARITMETICO> (0);
	this->b_techo = bitset<PRECISION_ARITMETICO> (1);
}

Caracter* Aritmetico::descomprimir_caracter(list<Caracter*>* distrib_frecuencias) {

	//Chequeo si me mandaron un ESC(1), se lo devuelvo al ppmc sin hacer nada
	if (distrib_frecuencias->size() == 1){
		return distrib_frecuencias->front();
	}
	//Leer el byte en el que estás
	bitset<PRECISION_ARITMETICO> byte_actual = this->bd->getBitsetActual();
	int posicion_actual = byte_actual.to_ulong();
	actualizarFrecuencias(distrib_frecuencias);
	//Fijarse en que rango cae y guardarse el caracter
	Caracter * c = buscarCaracterQueContiene(posicion_actual);
	//Actualizar los limites del aritmetico
	actualizarLimites(c);

	//Normalizar los limites y actualizar el byte en el que estás
	int resultado = normalizar();
		if (resultado == -1){
			//Hubo underflow
			bd->actualizarUnderflow(this->underflow_counter);
			this->underflow_counter = 0;
		} else
		if (resultado == 0){
			//Esto no deberia ocurrir, significa que no hubo underf ni overf
			cout << "Esta andando mal normalizar() de aritmetico" << endl;
			exit(1);
		}
		else {
			//si llegue hasta aca, resultado = cantidad de overflows
			bd->actualizarOverflow(resultado);
		}
	//Devolver el caracter
	return c;
}

Caracter *  Aritmetico::buscarCaracterQueContiene(int posicion){
	list<Particion*>::iterator it;
	for(it = this->particiones->begin(); it != this->particiones->end(); it++){
		if ((*it)->contiene(posicion)){
			return new Caracter ((*it)->getClave());
		}
	}
	return NULL;
}

void Aritmetico::comprimir(list<Caracter*>* distrib_frecuencias, Caracter* emision){
	//Inicia el proceso de normalizacion / emision del aritmetico
	//Si es el ultimo caracter tengo que emitir el piso
	string dato;
	emision->getDato(dato);
	if (dato == EoF){
		emitirPiso();
		return;
	}
	else if (distrib_frecuencias->size() == 1)
		return; //Si es un ESC(1) no se modifica nada
	actualizarFrecuencias(distrib_frecuencias);
	//Falta un paso, meterse en el caracter que te envian
	actualizarLimites(emision);
	normalizar();

}

void Aritmetico::emitirPiso(){
	//Se termina la compresion, se emite el piso
	//Hago que el piso sea igual al techo por la fuerza, para reutilizar el codigo
	//del metodo overflow()
	this->b_techo = this->b_piso;
	overflow();
	bb->terminarCompresion();
	return;
}

void Aritmetico::actualizarFrecuencias(list<Caracter*>* distribucion){
	//Metodo que recibe la lista con los caracteres y deja al aritmetico distribuido
	//de esa forma
	this->particiones->clear();
	//Cantidad por la que se van a dividir las apariciones para calcular la proba
	int cantidad_total = 0;
	int acumulador = this->techo;
	list<Caracter*>::iterator it;
	for(it = distribucion->begin(); it != distribucion->end(); it++){
		cantidad_total += (*it)->getContador();
	}
	list<Caracter*>::reverse_iterator rit;
	//Por cada caracter crea una particion con su intervalo
	for(rit = distribucion->rbegin(); rit != distribucion->rend(); rit++){
		Caracter * c_actual = *rit;
		string dato_actual;
		c_actual->getDato(dato_actual);
		//Este es un...
		double probabilidad = c_actual->getContador();
		probabilidad/= cantidad_total;
		//...modo raro de dividir
		int intervalo = (this->techo - this->piso) * probabilidad;
		//Si la iteracion es par suma uno al intervalo, para que redondee mas parejo
		Particion * p = new Particion(acumulador-intervalo+1, acumulador, dato_actual);
		particiones->push_front(p);
		acumulador -= intervalo;
	}
	corregirFrecuencias();
	return;
}

void Aritmetico::corregirFrecuencias(){
	//Asigno la probabilidad al primer caracter (esc / eof)
	particiones->front()->corregirPiso(this->piso);

}
void Aritmetico::actualizarLimites(Caracter*emision){
	//Busca donde esta la emision en el aritmetico actual y modificar los limites del
	//aritmetico total por estos
	list<Particion*>::iterator it;
	string dato;
	emision->getDato(dato);
	if (!emision)
		exit(1);
	for (it = particiones->begin(); it != particiones->end(); it++){
		if ((*it) -> contieneClave(dato)){
			int nuevo_piso = (*it)->getPiso();
			int nuevo_techo = (*it)->getTecho();
			this->piso = nuevo_piso;
			this->techo = nuevo_techo;
			//delete &b_piso;  Son necesarios?
			//delete &b_techo;
			this->b_piso = bitset<PRECISION_ARITMETICO> (nuevo_piso);
			this->b_techo = bitset<PRECISION_ARITMETICO> (nuevo_techo);
			//Si ya se actualizo, return
			return;
		}
	}
}


void Aritmetico::getBuffer(char ** compresion, int *largo){
	*compresion = bb->getBuffer();
	*largo = bb->getLongitud();
}

int Aritmetico::normalizar(){

	if (b_piso[POSICION_MSB] == b_techo[POSICION_MSB])
		return overflow();
	else if ((b_piso[POSICION_MSB] != b_techo[POSICION_MSB]))
		return underflow();
	return 0;
}

int Aritmetico::overflow(){
	//Actualiza la cantidad de overflows, devuelve la cantidad que hubo
	int cant_overflows = 0;
	int limit = 0;
	while (b_piso[POSICION_MSB] == b_techo[POSICION_MSB] && limit < PRECISION_ARITMETICO){
		//Emite el bit
		bool i = b_piso[POSICION_MSB];
		this->bb->escribirBit(i);
		while (underflow_counter > 0){
			//Emite el/los bit del UC
			this->bb->escribirBit(!i);
			this->underflow_counter--;
		}
		b_piso<<=1;	//Shift left de piso / techo
		b_techo<<=1; //Hay que ponerle 1s en los lugares donde fue shifteado
		b_techo.set(0,1);
		cant_overflows++;
		limit++;
	}
	//Actualiza los limites (int)
	this->piso = b_piso.to_ulong();
	this->techo = b_techo.to_ulong();

	return cant_overflows;
}

int Aritmetico::underflow(){
	//Limite controla la cantidad de interaciones del while, para que no entre
	//en un bucle infinito
	int limite = 0;
	bool msb_piso, msb_techo;
	//Me guardo el primer bit de c/u
	msb_piso = b_piso[POSICION_MSB];
	msb_techo = b_techo[POSICION_MSB];
	//A POSICION MSB le resto 1 para empezar desde el bit
	//de la derecha del MSB
	while (b_piso[POSICION_MSB - 1] != b_techo[POSICION_MSB - 1] && limite < PRECISION_ARITMETICO){
		underflow_counter ++;
		//Shifteo
		b_piso<<=1;
		b_techo<<=1;
		b_techo.set(0,1);
		limite++;
	}
	//Seteo el primer bit que me guarde
	b_piso.set(POSICION_MSB,msb_piso);
	b_techo.set(POSICION_MSB,msb_techo);

	//Actualiza los limites (int)
	this->piso = b_piso.to_ulong();
	this->techo = b_techo.to_ulong();

	return -1;
}

void Aritmetico::mostrarAritmetico(){
	cout << "Aritmetico: " << endl;
	cout << "Piso: ";
	cout << this->piso << endl;
	mostrarParticiones();
	cout << "Techo: ";
	cout << this->techo << endl;
}

void Aritmetico::mostrarParticiones(){
	list<Particion*>::iterator it;
	for (it = particiones->begin(); it != particiones->end(); it++){
		(*it)->mostrarParticion();
	}
}

void Aritmetico::mostrarListaDeCaracteres(list<Caracter*>* distrib_frecuencias){
	list<Caracter*>::iterator it;
	for (it = distrib_frecuencias->begin(); it != distrib_frecuencias->end(); it++){
		(*it)->mostrarCaracter();
	}

}

